const fs = require('fs');
const path = require('path');
const qiniu = require('qiniu');
const request = require('request');
const Duplex = require('stream').Duplex;
const sizeOf = require('image-size');
const mongoHelper = require('../lib/MongoHelper.js');
const DynamicJson = require('../core/dynamic-json.js').DynamicJSON;
const stringUtil = require('../lib/string-util.js');

module.exports = (io, settings) => {
    let logger = settings['logger'];

    const bucket = 'feheadline';
    const folder = 'compose';
    const imgprefix = settings['qiniu']['bucket_origin'][bucket];
    const qn_mac = new qiniu.auth.digest.Mac(settings['qiniu']['accessKey'], settings['qiniu']['secretKey']);
    const qn_config = new qiniu.conf.Config();

    const draft_col = new mongoHelper.MongoCollection(settings['mongo_connect_url'], 'draft', 5);
    const opus_col = new mongoHelper.MongoCollection(settings['mongo_connect_url'], 'opus', 5);
    const tplt_col = new mongoHelper.MongoCollection(settings['mongo_connect_url'], 'template', 5);

    const basic_component = new DynamicJson('basic-component.json', 300);
    const template_component = new DynamicJson('template-component.json', 300);

    const TIME_MARKER = 1514736000000;//2018-1-1

    const DEFAULT_PARTNER_CONFIG = {
        'iw_component' : [],
        'iw_url': 'http://iw.feheadline.com/',
        'authorization' : 'Y2hlcm9rZWU6Y2hlcm9rZWU='
    }

    const generateUniqueId = () => {
        return (Date.now().toString(36) + Math.random().toString(36).substr(2, 5)).toLowerCase();
    }

    const genSparkTime = (now=new Date()) => {
        return new Date(TIME_MARKER - (now.getTime() - TIME_MARKER));
    }

    const genSparkTimeString = _=>{
        return stringUtil.dateFormat(genSparkTime(),'yyyyMMddhhmmss');
    }

    const genUpToken = (category, ext)=>{
        let bucket = 'feheadline';
        let folder = 'compose';

        let policyOption = {
            'scope': bucket,
            'expires': 7200,
            'mimeLimit':'image/*;video/mp4;application/octet-stream;',
            'returnBody': '{"key":"$(key)","hash":"$(etag)","fsize":"$(fsize)","bucket":"$(bucket)","type":"$(mimeType)","width":"$(imageInfo.width)","height":"$(imageInfo.height)"}',
            'saveKey': folder + '/' + category + '/' + genSparkTimeString() + '$(fsize)'+(ext||'$(ext)')
            // 'saveKey': folder + '/' + category + '/$(year)$(mon)$(day)$(hour)$(min)$(sec)$(fsize)$(ext)'
        }

        let putPolicy = new qiniu.rs.PutPolicy(policyOption);
        let uploadToken = putPolicy.uploadToken(qn_mac);
        return uploadToken;
    }

    const qiniuPipeFile = (category, src, origin, size, callback) => {
        let options = {
                'uri':src,
                'headers':{
                    'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36'
                },
                'encoding':null
            }
        if(origin)options['headers']['Referer'] = origin;
        request(
            options,
            (error, response, body)=>{
                if(error || response.statusCode!=200){
                    logger.error('download error. '+(error?error.message:response.statusCode)+'. '+src+' FROM '+origin);
                    callback(error?error.message:'http code'+response.statusCode);
                }else{
                    logger.info('Download '+src+' from '+origin+' success.');
                    new Promise((resolve,reject)=>{
                        if(size && size.length>0){
                            try{
                               let real_size = sizeOf(body);
                                if(real_size){
                                    if(real_size['width'] == size[0] && real_size['height'] == size[1])resolve(body);
                                    else reject('incorrect image size, '+real_size['width']+'×'+real_size['height']+', expect '+size);
                                }else reject('image format error')
                            }catch(e){
                                reject('image format error');
                            } 
                        }else resolve(body);
                    }).then(buf=>{
                        let formUploader = new qiniu.form_up.FormUploader(qn_config);
                        let putExtra = new qiniu.form_up.PutExtra();
                        let stream = new Duplex();
                        stream.push(body);
                        stream.push(null);
                        formUploader.putStream(genUpToken(category), '', stream, putExtra, function(respErr, respBody, respInfo) {
                          if (respErr) {
                            logger.error('qiniu pipe file error. '+respErr.message);
                            callback(respErr.message);
                          }else{
                            if (respInfo.statusCode == 200) {
                                let type = respBody['type'];
                                let key = respBody['key'];
                                if(type.startsWith('image')){
                                    callback(null, imgprefix + '/' + key);
                                }else{
                                    callback('invalidate file type: '+type);
                                }
                            } else {
                                callback('error, code:'+respInfo.statusCode);
                            }
                          }    
                        });
                    }).catch(err=>{
                        logger.error('qiniu pipe file error: '+err);
                        callback(err);
                    });
                }
            }
            );
    }

    const qiniuFetchFile = (category, src, callback) => {
        let bucketManager = new qiniu.rs.BucketManager(qn_mac, qn_config);
        let key = folder + '/' + category + '/' + genSparkTimeString() + parseInt(1 + Math.random()*1000000)
        bucketManager.fetch(src, bucket, key, function(err, respBody, respInfo) {
          if (err) {
            logger.error('qiniu fetch file error. '+err);
            callback(err.message);
          } else {
            if (respInfo.statusCode == 200) {
                logger.info('Fetch '+src+' success.');
                let type = respBody['mimeType'];
                let key = respBody['key'];
                if(type.startsWith('image')){
                    callback(null, imgprefix + '/' + key);
                }else{
                    callback('invalidate file type: '+type);
                }
            } else {
                callback('error, code:'+respInfo.statusCode);
            }
          }
        });
    }

    //update and insert draft meta values
    const upsertDraftMeta = async(pkey, akey, values) =>{
        let _id = `${pkey}-${akey}`;
        let ret = await draft_col.update(_id, values, false, true);
        return ret.modifiedCount;
    }

    //check article , if not exists create it
    const createDraftIfNotExist = async(pkey, akey, ukey) =>{
        let _id = `${pkey}-${akey}`;
        let doc = await draft_col.get(_id, ['_id']);
        if(doc && doc['_id'])return true;
        else{
            let now = new Date();
            let c = await draft_col.save({
             '_id':_id, 
             'pkey': pkey, 
             'akey': akey,
             'ukey': ukey, 
             'title': '从这里开始', 
             'sections':[],
             'published':false,
             'version': now.getFullYear()+'.'+(now.getMonth()+1)+'.'+now.getDate(),
             'updated':new Date()
         });
            return c.insertedCount>0;
        }
    }

    //insertSection
    const insertSection = async(pkey, akey, skey, value, type, css, label, insert_postion) =>{
        let _id = `${pkey}-${akey}`;
        let updoc = {
            '$push' : {
                'sections' : {
                    '$each' : [
                        {
                            'id' : skey,
                            'type' : type,
                            'css' : css,
                            'label' : label,
                            'html' : value
                        }
                    ],
                    '$position' : insert_postion>=0 ? insert_postion : 0 
                } 
            }
        }
        let ret = await draft_col.update(_id, updoc, true, true);
        return ret.modifiedCount;
    }

    //update section
    const updateSection = async(pkey, akey, skey, value) =>{
        let _id = `${pkey}-${akey}`;
        let query = {
            '_id' : _id,
            'sections.id' : skey
        }
        let updoc = {
            '$set' : {
                'sections.$.html' : value
            }
        }
        let ret = await draft_col.execute(0,
            async(self, db) => {
                return await db.collection(self.mongo_collection).updateOne(query, updoc, { 'upsert': false, 'w': 1 });
            });
        return ret.modifiedCount;
    }

    const removeSection = async(pkey, akey, skey) =>{
        let _id = `${pkey}-${akey}`;
        let updoc = {
            '$pull' : {
                'sections' : {
                    'id' : skey
                } 
            }
        }
        let ret = await draft_col.update(_id, updoc, true, false);
        return ret.modifiedCount;
    }

    const loadPartnerConfig = (pkey) =>{
        let dj = new DynamicJson(pkey+'.json', 300);
        return new Promise((resolve, reject)=>{
            dj.get().then(code=>{
                resolve(Object.assign(DEFAULT_PARTNER_CONFIG, code));
            }).catch(err=>{
                reject(err);
            });
        });  
    }

    const loadDraft = (pkey, akey) => {
        return getDaft(`${pkey}-${akey}`);
    }

    const getDaft = async (_id) => {
        let doc = await draft_col.get(_id);
        if(doc && doc['_id'])return doc;
        else return null;
    }

    const loadBasicComponent = (pkey, akey) =>{
        return basic_component.get();
    }

    const loadTemplateComponent = (pkey, akey, ukey, cate) =>{
        let query;
        if(cate=='private'){
            query = {
               ukey,
               'grant': cate 
            }
        }else{
            query = {
                'pkey': pkey,
                'grant': cate
            }
        }
        return new Promise((resolve, reject)=>{
            tplt_col.find(query, null , {'updated':-1}, 100, 1).then(ret=>{
                if(ret.list.length>0){
                    resolve(ret.list);
                }else reject('no result')
            });
        });
    }

    const loadIwComponent = (pkey, akey) =>{
        return Promise.resolve([]);
    }

    const saveTemplate = async (pkey, akey, ukey, label, cate, desc, html, snapshot) =>{
        let c = await tplt_col.save({
            pkey, 
            akey, 
            ukey, 
            label,
            'grant': cate,
            'description': desc,
            'template': html,
            'img': snapshot,
            'updated': new Date()
        });
        return c.insertedCount>0?c.ops[0]['_id'].toString():null;
    }

    const removeTemplate = async (ukey, key) =>{
        let c = await tplt_col.remove({
            ukey, 
            '_id': key
        });
        return c.deletedCount>0;
    }

    const searchDraft = async(ukey, pkey, condition)=>{
        let query = {};
        if(condition['include_group_draft']===true)query['pkey'] = pkey;
        else query['ukey'] = ukey;
        if(condition['date_range'].length>0 && condition['date_range'][0] && condition['date_range'][1]){
            query['updated'] = {
                '$gte' : condition['date_range'][0],
                '$lte' : condition['date_range'][1]
            }
        }
        if(condition['keywords'])query['title'] = {'$regex' : condition['keywords']}
        return new Promise((resolve, reject)=>{
            draft_col.find(query, ['title', 'updated', 'published', 'publish_key', 'ukey', 'akey', 'version'] , {'updated':-1}, condition['page_size'], condition['page_number']).then(ret=>{
                if(ret.list.length>0){
                    resolve(ret);
                }else reject('no result')
            });
        });
    }

    const cloneDraft = (ukey, pkey, id)=>{
        return new Promise((resolve, reject)=>{
            getDaft(id).then(doc=>{
                if(doc){
                        let now = new Date();
                        let t_akey = generateUniqueId();
                        let ndoc = {
                             '_id':`${pkey}-${t_akey}`, 
                             'pkey': pkey, 
                             'akey': t_akey, 
                             'ukey': ukey,
                             'title': doc['title']+'(副本)', 
                             'sections':doc['sections'],
                             'published':false,
                             'version': now.getFullYear()+'.'+(now.getMonth()+1)+'.'+now.getDate(),
                             'updated':new Date()
                         }
                        draft_col.save(ndoc).then(c=>{
                            if(c.insertedCount>0){
                                delete ndoc['sections'];
                                resolve(ndoc);
                            }else reject('clone draft '+id+' failure');
                         });
                }else reject('draft '+id+' not found');
            });
        });
    }

    const deleteDraft = (ukey, pkey, id)=>{
        return new Promise((resolve, reject)=>{
            draft_col.remove({
                ukey, 
                '_id': id
            }).then(c=>{
                if(c.deletedCount>0)resolve(true);
                else reject('delete draft '+id+' failure');
            });
        });
    }

    const publishDraft = (pkey, akey, ukey) =>{
        return new Promise((resolve, reject)=>{
            loadDraft(pkey, akey).then(doc=>{
                if(doc){
                        let _id = doc['_id'];
                        let now = new Date();
                        let ndoc = {
                            'published': true,
                            'publish_date': new Date(),
                            'publish_key': ''
                        }

                        opus_col.update(_id, Object.assign(doc, ndoc), false, true).then(ret=>{
                            if(ret.modifiedCount>0){
                                draft_col.update(_id, ndoc, false, false).then(ret2=>{
                                    if(ret2.modifiedCount>0){
                                        resolve(_id);
                                    }else reject('draft '+_id+' published, but sync status failure.');
                                });
                            }else reject('draft '+_id+' can not sync to opus.');
                        });
                }else reject('draft '+_id+' not found');
            });
        });
    }

///////////////////////////////////////////////////socket handle////////////////////////////////////////////////////////////////////////

    let nsp = io.of('/editor');//io.of('/editor').to('newid').emit(msg);

    nsp.on('connection', function(socket) {
        let socketId = socket.id;
        let remoteAddress = socket.client.conn.remoteAddress || 'client';
        let idStr = `${socketId}(${remoteAddress})`;
        let pkey, akey, ukey;

        socket.on('disconnect', function() {
        });

        socket.on('join', function(_pkey, _akey, _ukey, callback) {
            pkey = _pkey;
            akey = _akey;
            ukey = _ukey;
            socket.join(akey);
            logger.debug(idStr + ' edit article '+_akey+' who comes from '+_pkey);
            if(akey){
                createDraftIfNotExist(_pkey, _akey, _ukey).then(success=>{
                    callback(success?null:'init error', _pkey, _akey);
                });
            }else{
                callback(null, _pkey, _akey);
            }
        });

        socket.on('get-qiniu-setting', function(callback) {
            callback({
                'bucket_origin' : settings['qiniu']['bucket_origin']['feheadline']
            });
        });

        socket.on('quniu-uptoken', function(ext, callback) {
            callback(genUpToken(pkey, ext));
        });

        socket.on('quniu-list-file', function(marker, prefix, limit, callback) {
            let bucket = 'feheadline';
            let folder = 'compose';

            let bucketManager = new qiniu.rs.BucketManager(qn_mac, qn_config);
            let options = {
              'limit': limit,
              'prefix': folder + '/' + pkey + '/' + prefix,
              'marker': marker
            };


            bucketManager.listPrefix(bucket, options, function(err, respBody, respInfo) {
              if (err) {
                logger.error(err);
                callback(err);
              }else{
                if (respInfo.statusCode == 200) {
                    let nextMarker = respBody.marker;
                    // let commonPrefixes = respBody.commonPrefixes;
                    let items = respBody.items;
                    // items.forEach(function(item) {
                    //   console.log(item.key);
                    //   // console.log(item.putTime);
                    //   // console.log(item.hash);
                    //   // console.log(item.fsize);
                    //   // console.log(item.mimeType);
                    //   // console.log(item.endUser);
                    //   // console.log(item.type);
                    // });
                    callback(null, items, nextMarker);
                  } else {
                    callback(respInfo.statusCode);
                  }
              } 
            });
        });

        socket.on('put-image', function(src, origin, size, callback) {
            qiniuPipeFile(pkey, src, origin, size, callback);
        });

        socket.on('fetch-image', function(src, callback) {
            qiniuFetchFile(pkey, src, callback);
        });

        socket.on('update-meta', function(values, callback) {
            let doc = {};
            for(let k of Object.keys(values)){
                let fmt_k = k.replace(/^_/,'');
                let fmt_v = values[k];
                if(fmt_k=='title')fmt_v = fmt_v.replace(/<\/?[^>]+>/ig,'').replace(/&[^;]+;/ig,'');
                doc[fmt_k] = fmt_v;
            }
            upsertDraftMeta(pkey, akey, doc).then(c=>{
                socket.to(akey).emit('update-meta', values);//broadcast exception sender
                callback(c>=0?null:'update fail');
            });
        });

        socket.on('update-section', function(skey, value, callback) {
            updateSection(pkey, akey, skey, value).then(ok=>{
                socket.to(akey).emit('update-section', skey, value);//broadcast exception sender
                callback(ok?null:'update section fail');
            })
        });

        socket.on('remove-section', function(skey, callback) {
            removeSection(pkey, akey, skey).then(ok=>{
                socket.to(akey).emit('remove-section', skey);
                callback(ok?null:'update section fail');
            })
        });

        socket.on('new-section', function(skey, value, insert_postion, type, css, label, callback) {
            insertSection(pkey, akey, skey, value, type, css, label, insert_postion).then(ok=>{
                socket.to(akey).emit('new-section', skey, value, type, css, label, insert_postion);//broadcast exception sender
                callback(ok?null:'insert section fail');
            })
        });

        socket.on('load-draft', function(callback) {
            loadDraft(pkey, akey).then(doc=>{
                callback(doc==null?'draft not found':null, doc);
            });
        });

        socket.on('load-basic-component', function(callback) {
            loadBasicComponent(pkey, akey).then(json=>{
                callback(null, json);
            }).catch(err=>{
                callback(err);
            });
        });

        socket.on('load-template-component', function(cate, callback) {
            loadTemplateComponent(pkey, akey, ukey, cate).then(json=>{
                callback(null, json);
            }).catch(err=>{
                callback(err);
            });
        });

        socket.on('load-partner-config', function(callback) {
            loadPartnerConfig(pkey).then(json=>{
                callback(null, json);
            }).catch(err=>{
                callback(err);
            });
        });

        socket.on('save-template', function(label, cate, desc, html, snapshot, callback) {
            saveTemplate(pkey, akey, ukey, label, cate, desc, html, snapshot).then(id=>{
                callback(id);
            });
        });

        socket.on('remove-template', function(key, callback) {
            removeTemplate(ukey, key).then(ok=>{
                callback(ok ? null : 'remove template error');
            });
        });

        socket.on('search-draft', function(condition, callback) {
            searchDraft(ukey, pkey, condition).then(docs=>{
                callback(null, docs);
            }).catch(err=>{
                callback(err);
            });
        });

        socket.on('clone-draft', function(id, callback) {
            cloneDraft(ukey, pkey, id).then(doc=>{
                callback(null, doc);
            }).catch(err=>{
                callback(err);
            });
        });

        socket.on('delete-draft', function(id, callback) {
            deleteDraft(ukey, pkey, id).then(ok=>{
                callback(null);
            }).catch(err=>{
                callback(err);
            });
        });

        socket.on('publish-draft', function(callback) {
            publishDraft(pkey, akey, ukey).then(id=>{
                callback(id);
            }).catch(err=>{
                callback(null);
            });
        });
    });
}